package com.kingmed.kmss.bpmn.flow.service.base;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kingmed.kmss.bpmn.flow.command.ProcessSqlCommand;
import com.kingmed.kmss.bpmn.flow.command.TaskSqlCommand;
import com.kingmed.kmss.bpmn.flow.helper.*;
import com.kingmed.kmss.bpmn.flow.utils.FlowableBeanUtils;
import com.kingmed.kmss.bpmn.modules.backup.service.BizApprovalDataService;
import com.kingmed.kmss.common.bo.bpmn.*;
import com.kingmed.kmss.common.constant.bpmn.BpmnConstant;
import com.kingmed.kmss.common.constant.bpmn.ProcessConstant;
import com.kingmed.kmss.common.constant.marketing.MktConstants;
import com.kingmed.kmss.common.qc.bpmn.ProcessQueryCondition;
import com.kingmed.kmss.common.qc.bpmn.TaskQueryCondition;
import com.kingmed.kmss.common.utils.BeanCopyUtils;
import com.kingmed.kmss.common.utils.ProcessUtils;
import com.kingmed.kmss.common.utils.StringUtils;
import com.kingmed.kmss.framework.feign.client.admin.CommonAdminFeignServiceFacade;
import com.kingmed.kmss.framework.feign.client.dict.DictFeignServiceFacade;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.idm.api.Group;
import org.flowable.idm.api.User;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 自定义的service，没有直接import flowable原生的service，引入了各类helper（自己做封装）
 * ！！！不建议使用！！！
 */
@Slf4j
@Service
public class BpmCustomServiceImpl {

    @Autowired
    @Getter
    ProcessHelper processHelper;

    @Autowired
    @Getter
    TaskHelper taskHelper;

    @Autowired
    @Getter
    HistoryHelper historyHelper;

    @Autowired
    @Getter
    RepositoryHelper repositoryHelper;

    @Autowired
    @Getter
    IdentityHelper identityHelper;

    @Autowired
    @Getter
    ProcessSqlCommand processSqlCommand;

    @Autowired
    @Getter
    TaskSqlCommand taskSqlCommand;

    @Autowired
    @Getter
    CommonAdminFeignServiceFacade remoteAdminSvc;

    @Autowired
    @Getter
    DictFeignServiceFacade dictFeignSvc;

    @Autowired
    BizApprovalDataService bizApprovalDataService;

    /**
     * 部署流程定义文件
     *
     * @param deployBO
     * @return
     */
    public ProcessDefinitionBO deployProcessDefinitionByInputStream(ProcessDefinitionBO deployBO) {

        Deployment deployment = repositoryHelper.deployProcessDefinitionByClasspathResource(deployBO);

        return processSqlCommand.findLatestVersionProcessDefinitionByKey(deployment.getKey());

    }

    /**
     * 开启一个流程实例
     *
     * @param starterBO
     * @return
     */
    public BpmnBO startProcessInstance(ProcessStarterBO starterBO) {

        BpmnBO bpmBO = new BpmnBO();
        if (StringUtils.isEmpty(starterBO.getBusinessType())) {
            starterBO.setBusinessType("universal_process");
        }
        // 生成唯一的业务流程编号businessKey
        String bizType = starterBO.getBusinessType();

        // 根据business type和business id生成business key, 方便task查询以满足流程需要
        String businessKey = ProcessUtils.generateBusinessKey(bizType, starterBO.getBusinessId());
        starterBO.setBusinessKey(businessKey);

        // 处理instance name
        String instanceName = generateInstanceName(starterBO);
        starterBO.setInstanceName(instanceName);
        
        // 将保存审批跟踪用的数据放到proc var中
        ProcessVariableBO processVars = starterBO.getProcVars();
        if (processVars == null) {
        	processVars = new ProcessVariableBO();
        }

        Map<String, Object> varMap = processVars.getVarMap();
        if (varMap == null) {
        	varMap = new HashMap<>();
        }
        
        String userRealName = remoteAdminSvc.getUserRealName(starterBO.getStartUserId());
       
        varMap.put(BpmnConstant.VarKeys.BIZ_ID, starterBO.getBusinessId());
        varMap.put(BpmnConstant.VarKeys.BIZ_TYPE, starterBO.getBusinessType());
        varMap.put(BpmnConstant.VarKeys.BIZ_KEY, businessKey);
        varMap.put(BpmnConstant.VarKeys.PARENT_ORG_ID, starterBO.getParentOrgId());
        varMap.put(BpmnConstant.VarKeys.COMMENT, starterBO.getComment());
        varMap.put(BpmnConstant.VarKeys.ORG_TYPE, starterBO.getOrgType());
        varMap.put(BpmnConstant.VarKeys.USER_NAME, userRealName);
        //子公司，大区，总部，三选一
        String orgName = remoteAdminSvc.getManageOrgNameByUsername(starterBO.getStartUserId());
        varMap.put(BpmnConstant.VarKeys.ORG_NAME, orgName);
        varMap.put(BpmnConstant.VarKeys.PROCESS_TYPE, starterBO.getProcessType());
       
        log.debug("startProcessInstance(): varMap: {}", varMap);
        Map<String, String> bizTypeDict = dictFeignSvc.getSysDictMap(ProcessConstant.DictCode.PROCESS_BUSINESS_TYPE);
        String bizTypeName = bizTypeDict.get(starterBO.getBusinessType());
        varMap.put(BpmnConstant.VarKeys.BIZ_TYPE_NAME, bizTypeName);
        
        
        processVars.setVarMap(varMap);
        starterBO.setProcVars(processVars);

        //如果有业务报文，保存业务报文
//        bizApprovalDataService.submitApprovalData(varMap);
        
        // TODO: 判断是否有当前bizKey还未结束的流程实例， 正式使用要做判断？？
        ProcessInstance processInstance = this.processHelper.startProcessInstance(starterBO);
        ProcessInstanceBO procInstBO = this.findProcessInstanceByProcessInstanceId(processInstance.getId())
                .get(0);

        Task task = this.taskHelper.findSingleTaskByExecutionId(procInstBO.getExecutionId());
        //
        procInstBO.setFocusTaskId(task.getId());
        procInstBO.setStartUserId(starterBO.getStartUserId());
        procInstBO.setBusinessKey(businessKey);
        procInstBO.setInstanceName(instanceName);
        procInstBO.setInstanceId(processInstance.getProcessInstanceId());
        TaskBO taskBO = this.taskSqlCommand.findTaskById(task.getId());

        List<IdentityLink> identityLinkList = this.taskHelper.getIdentityLink(task.getId());
        IdentityLinkBO identityLinkBO = new IdentityLinkBO();
        IdentityLink identityLink;
        if (!identityLinkList.isEmpty()) {
            identityLink = identityLinkList.get(0);
            identityLinkBO.setGroupId(identityLink.getGroupId());
            identityLinkBO.setType(identityLink.getType());
            taskBO.setGroupId(identityLink.getGroupId());
        }

        identityLinkBO.setUserId(starterBO.getStartUserId());
        //
        taskBO.setAssigneeId(starterBO.getStartUserId());
        //
        bpmBO.setProcessInstanceBO(procInstBO);
        bpmBO.setFocusTaskBO(taskBO);
        bpmBO.setFocusIdentityLinkBO(identityLinkBO);
        bpmBO.setProcessVariableBO(starterBO.getProcVars());
        
        return bpmBO;
    }

	

    /**
     * 或未指定instance name则根据business type和business id生成
     * @param starterBO
     * @return
     */
    private String generateInstanceName(ProcessStarterBO starterBO) {
		
    	String instanceName = starterBO.getInstanceName();
    	
    	if (StringUtils.isEmpty(instanceName)) {
    		// TODO: 改成使用流程定义名称不用id??
    		instanceName = String.format("%s:%s", starterBO.getBusinessType(), starterBO.getBusinessId());
    	}
		return instanceName;
	}

	public void updateProcessInstance(ProcessInstanceBO piBO) {
        this.processHelper.updateProcessInstance(piBO);
    }

    public void setProcessVariables(ProcessVariableBO varBO) {
        if (varBO != null) {
            Map<String, Object> varMap = BeanUtil.beanToMap(varBO);
            varMap.remove("executionId");
            this.processHelper.setProcessVariables(varBO.getExecutionId(), varMap);
        }
    }

    public ProcessVariableBO getProcessVariables(String executionId) {
        Map<String, Object> varMap = this.processHelper.getProcessVariables(executionId);
        ProcessVariableBO varBO = new ProcessVariableBO();
        varBO = BeanUtils.mapToBean(varMap, ProcessVariableBO.class);
        return varBO;
    }

    public ProcessInstanceBO findProcessInstanceByExecutionId(String executionId) {
        return processSqlCommand.findProcessByExecutionId(executionId);
    }

    public List<ProcessInstanceBO> findProcessInstanceByProcessInstanceId(String processInstanceId) {
        List<ProcessInstanceBO> resultList = processSqlCommand.findProcessByInstanceId(processInstanceId);
        return resultList;
    }

    public List<ProcessInstanceBO> findProcessInstanceByBizKey(String bizKey) {
        List<ProcessInstanceBO> resultList = processSqlCommand.findProcessByBizKey(bizKey);
        return resultList;
    }

    public List<ProcessInstanceBO> findProcessInstanceByCondition(ProcessQueryCondition processQryCond) {
        List<ProcessInstanceBO> resultList = processSqlCommand.findProcessByCondition(processQryCond);
        return resultList;
    }

    public List<ProcessInstanceBO> findProcessInstanceByProcessDefinitionKey(String processDefinitionKey) {

        ProcessQueryCondition processQryCond = new ProcessQueryCondition();
        processQryCond.setProcessDefinitionKey(processDefinitionKey);

        List<ProcessInstanceBO> resultList = processSqlCommand.findProcessByCondition(processQryCond);
        return resultList;
    }

    public List<ProcessInstanceBO> findProcessInstanceByCategory(String category) {

        ProcessQueryCondition processQryCond = new ProcessQueryCondition();
        processQryCond.setProcessDefinitionCategory(category);

        List<ProcessInstanceBO> resultList = processSqlCommand.findProcessByCondition(processQryCond);
        return resultList;
    }

    public List<ProcessInstanceBO> findProcessInstanceByBusinessKey(String businessKey) {

        ProcessQueryCondition processQryCond = new ProcessQueryCondition();
        processQryCond.setBusinessKey(businessKey);

        List<ProcessInstanceBO> resultList = processSqlCommand.findProcessByCondition(processQryCond);
        return resultList;
    }


    public void saveTask(ProcessVariableBO varBO, CommentBO commentBO) {
        this.setProcessVariables(varBO);
        this.saveComment(commentBO);
    }

    public void completeTask(TaskBO taskBO) {
        if (taskBO.getProcVars() != null && taskBO.getProcVars().getExecutionId() != null)
            this.setProcessVariables(taskBO.getProcVars());
        if (taskBO.getCommentBO() != null && StrUtil.isNotEmpty(taskBO.getCommentBO().getMessage()))
            this.saveComment(taskBO.getCommentBO());
        this.taskHelper.completeTask(taskBO.getTaskId());
        //
    }

    public void completeTask(ProcessVariableBO varBO, CommentBO commentBO) {
        this.setProcessVariables(varBO);
        this.saveComment(commentBO);

//		TaskBO taskBO = this.findTaskById(varBO.getFocusTaskId());
        this.taskHelper.completeTask(commentBO.getTaskId());
        //
    }
    
    @Transactional
    public String completeTask(String taskId, Map<String, Object> vars) {
      //  this.setProcessVariables(varBO);
        //this.saveComment(commentBO);

//		TaskBO taskBO = this.findTaskById(varBO.getFocusTaskId());
        this.taskHelper.completeTask(taskId, vars);
        return taskId;
    }


    public UserBO findUserById(String userId) {
        return processSqlCommand.findUserById(userId);
    }


    public List<TaskBO> findMyTask(String userId, String groupId) {
        TaskQueryCondition qryCond = new TaskQueryCondition();
        qryCond.setCandidateUserId(userId);
        qryCond.setGroupId(groupId);
        qryCond.setAssigneeId(userId);
        return taskSqlCommand.findMyTask(qryCond);
    }

    public Page<TaskBO> findMyTask(String userId, String groupId, Page<TaskBO> page) {
        TaskQueryCondition qryCond = new TaskQueryCondition();
        qryCond.setCandidateUserId(userId);
        qryCond.setGroupId(groupId);
        qryCond.setAssigneeId(userId);
        int startIndex = page.getCurrent() <= 0 ? 0 : (int) ((page.getCurrent() - 1) * page.getSize());
        List<TaskBO> list = taskSqlCommand.findMyTaskPage(qryCond, startIndex, (int) page.getSize());
        return page.setRecords(list);
    }

    public long findMyTaskCount(String userId, String groupId) {
        TaskQueryCondition qryCond = new TaskQueryCondition();
        qryCond.setCandidateUserId(userId);
        qryCond.setGroupId(groupId);
        qryCond.setAssigneeId(userId);
        return taskSqlCommand.findMyTask(qryCond).size();
    }

    public CommentBO findCommentByTaskAndUserId(String taskId, String userId) {
        CommentBO commentBO = this.taskSqlCommand.findCommentByTaskAndUserId(taskId, userId);
        return commentBO;
    }

    public List<CommentBO> findCommentByTaskId(String taskId) {
        return this.taskSqlCommand.findCommentByTaskId(taskId);
    }

    public List<CommentBO> findCommentByProcessInstanceId(String processInstanceId) {
        return this.taskSqlCommand.findCommentByProcessInstanceId(processInstanceId);
    }

    public CommentBO saveComment(CommentBO commentBO) {
        Comment flowableComment;
//		if (commentBO.getId() == null || commentBO.getId().trim().length() == 0) {
        flowableComment = this.taskHelper.addComments(commentBO.getTaskId(), commentBO.getProcessInstanceId(),
                commentBO.getType(), commentBO.getMessage());
//		} else {
//			flowableComment = this.taskHelper.findCommentById(commentBO.getId());
//		}
        flowableComment.setUserId(commentBO.getUserId());
        this.taskHelper.updateComment(flowableComment);
        //
        commentBO.setId(flowableComment.getId());
        return commentBO;
    }

    public void claimTask(String taskId, String claimer) {
        this.taskHelper.claimTask(taskId, claimer);
    }


    public void unclaimTask(String taskId) {
        this.taskHelper.unclaimedTask(taskId);
    }

    public void assignTask(String taskId, String claimer) {
        this.taskHelper.assignTask(taskId, claimer);
    }

    public ProcessDefinitionBO findProcessDefinitionById(String processDefinitionId) {
        return this.processSqlCommand.findProcessDefinitionById(processDefinitionId);
    }

    public TaskBO findTaskById(String taskId) {
        return taskSqlCommand.findTaskById(taskId);
    }

    public List<TaskBO> findTaskByCondition(TaskQueryCondition qryCond) {
        return taskSqlCommand.findTaskByCondition(qryCond);
    }

    public void endProcess(ProcessInstanceBO processInstanceBO) {
        if (!historyHelper.isProcessEnd(processInstanceBO.getInstanceId())) {
            processHelper.endProcess(processInstanceBO);
        }
    }

    public boolean isProcessEnd(String processInstanceId) {
        return historyHelper.isProcessEnd(processInstanceId);
    }

    public void createAttachment(AttachmentBO attachmentbo) {
        taskHelper.createAttachment(attachmentbo);
    }

	public List<UserBO> getUserList() {
		List<User> users = identityHelper.getUserList();
		List<UserBO> userBOs = FlowableBeanUtils.toUserBO(users);

		userBOs.forEach(user -> {
			List<Group> groups = identityHelper.getGroupsByUserId(user.getUserId());
			user.setGroups(FlowableBeanUtils.toGroupBO(groups));
		});
		
		return userBOs;
	}
    
	@Transactional(rollbackFor = Exception.class)
	public void createUser(UserBO userBO) {
		
		identityHelper.createUser(userBO);
	}
	
	@Transactional(rollbackFor = Exception.class)
	public void assignUserToGroups(UserBO userBO) {
		User user = identityHelper.getUserById(userBO.getUserId());

		if (user == null) {
			identityHelper.createUser(userBO);
		}
		
		identityHelper.assignUserToGroups(userBO);
	}

	public List<GroupWithUserBO> getGroupList() {
		
		List<Group> groups = identityHelper.getGroupList();
		List<GroupBO> groupBOs = FlowableBeanUtils.toGroupBO(groups);
		
		List<GroupWithUserBO> results = BeanCopyUtils.copyList(groupBOs, GroupWithUserBO.class);
		results.forEach(g -> {
			List<User> users = identityHelper.getGroupUsers(g.getGroupId());
			g.setUsers(FlowableBeanUtils.toUserBO(users));
		});
		
		return results;
	}
	
	@Transactional(rollbackFor = Exception.class)
	public void createGroup(GroupBO groupBO) {

		identityHelper.createGroup(groupBO);
	}

	@Transactional(rollbackFor = Exception.class)
	public void deleteUser(UserBO userBO) {
		identityHelper.deleteUser(userBO);
		
	}

	@Transactional(rollbackFor = Exception.class)
	public void unassignUserFromGroups(UserGroupBO userGroupBO) {
		identityHelper.unassignUserFromGroup(userGroupBO);
		
	}

	@Transactional(rollbackFor = Exception.class)
	public void deleteGroup(GroupBO groupBO) {
		identityHelper.deleteGroup(groupBO);
		
	}

	public List<TaskBO> processtasks(String processInstanceId) {
		
		List<Task> tasks = taskHelper.findTaskByProcessInstanceId(processInstanceId);
		List<TaskBO> taskDtos = BeanCopyUtils.copyList(tasks, TaskBO.class);
		
		return taskDtos;
	}
	public Page<TaskBO> getTasksByUser(String userId, String bizType, int pageNum, int pageSize, String startTime, String endTime) {
		
		int startResult = (pageNum-1) * pageSize;
		int maxResult = pageNum * pageSize;
		
		String userIdEx = userId;
		if (StringUtils.isNotEmpty(userId)) {
			userIdEx = String.format("%%%s%s%%", userId, MktConstants.LIST_STRING_SEPERATOR_2);

		}
		
		long total = taskHelper.countTaskByAssignee(userId, userIdEx, bizType,startTime,endTime);
		
		List<Task> tasks = taskHelper.findTaskByAssignee(userId, userIdEx, bizType, startResult, maxResult,startTime,endTime);
		
		log.info("getTasksByUser size={}", tasks.size());

		List<TaskBO> taskDtos = BeanCopyUtils.copyList(tasks, TaskBO.class);
		
		if (CollUtil.isEmpty(taskDtos)) {
			return null;
		}
		
		for(int i=0; i< taskDtos.size(); ++i) {
			TaskBO taskBO = taskDtos.get(i);
			Task task = tasks.get(i);
			
			Map<String, Object> vars = taskHelper.getTaskVariables(task.getId());
			
			//String bizTitle = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.BIZ_TITLE);
			String bizTitle = (String)vars.get(BpmnConstant.VarKeys.BIZ_TITLE);
			taskBO.setBizTitle(bizTitle);
			
			if (StringUtils.isEmpty(bizTitle)) {
				bizTitle =processHelper.getProcessVariable(task.getExecutionId(), BpmnConstant.VarKeys.BIZ_TITLE);				
			}
			
			ProcessInstance procInst = processHelper.getProcessInstance(task.getProcessInstanceId());
		//	String submitTime1 = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.SUBMIT_TIME);
		//	Date submitTime = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.SUBMIT_TIME);
	        taskBO.setStartTime(procInst.getStartTime());
	     //   taskBO.setVars(processHelper.getProcessVariables(task.getExecutionId()));
	        taskBO.setVars(vars);
			taskBO.setAssigneeId(task.getAssignee());
			taskBO.setExecutionId(task.getExecutionId());
			taskBO.setTaskId(task.getId());
			
			taskBO.setParentTaskId(task.getParentTaskId());
			taskBO.setTaskName(task.getName());
			taskBO.setProcessDefinitionId(task.getProcessDefinitionId());
			taskBO.setProcessInstanceId(task.getProcessInstanceId());
			//String userName = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.USER_NAME);
			String userName = (String)vars.get(BpmnConstant.VarKeys.USER_NAME);
			taskBO.setUserName(userName);
		//	String orgName = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.ORG_NAME);
			String orgName = (String)vars.get(BpmnConstant.VarKeys.ORG_NAME);
			taskBO.setOrgName(orgName);
		//	Object tmp = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.BIZ_ID);
			Object tmp = vars.get(BpmnConstant.VarKeys.BIZ_ID);
			String bizId = "";
			if (tmp instanceof Long) {
				bizId = String.valueOf(tmp);	
			} else if (tmp instanceof String) {
				bizId = (String)tmp;
			}
			taskBO.setBizId(bizId);
		//	String bizTypeEx = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.BIZ_TYPE);
			String bizTypeEx = (String)vars.get(BpmnConstant.VarKeys.BIZ_TYPE);
			taskBO.setBizType(bizTypeEx);
		//	String bizTypeName = taskHelper.getTaskVariable(task.getId(), BpmnConstant.VarKeys.BIZ_TYPE_NAME);
			String bizTypeName = (String)vars.get(BpmnConstant.VarKeys.BIZ_TYPE_NAME);
			taskBO.setBizTypeName(bizTypeName);
			
		}
		
		//
		Page<TaskBO> page = new Page<TaskBO>(pageNum, pageSize,
				total, false);

	
		// set the dto list to page
		page.setRecords(taskDtos);

		return page;

	}
    public Integer getTasksNumByUser(String userId, String bizType) {

        String userIdEx = userId;
        if (StringUtils.isNotEmpty(userId)) {
            userIdEx = String.format("%%%s%s%%", userId, MktConstants.LIST_STRING_SEPERATOR_2);

        }

        long total = taskHelper.countTaskByAssignee(userId, userIdEx, bizType,null,null);
        return Long.valueOf(total).intValue();

    }
	public List<TaskBO> getCurrTasksByProcInstId(String procInstId) {
		List<Task> tasks = taskHelper.findTaskByProcessInstanceId(procInstId);
		log.info("getCurrTasksByProcInstId size={}", tasks.size());

		List<TaskBO> taskDtos = BeanCopyUtils.copyList(tasks, TaskBO.class);
		
		if (CollUtil.isEmpty(taskDtos)) {
			return taskDtos;
		}
		
		for(int i=0; i< taskDtos.size(); ++i) {
			TaskBO taskBO = taskDtos.get(i);
			Task task = tasks.get(i);
			
			taskBO.setAssigneeId(task.getAssignee());
			taskBO.setExecutionId(task.getExecutionId());
			taskBO.setTaskId(task.getId());
            taskBO.setTaskDefinitionId(task.getTaskDefinitionId());
            taskBO.setTaskDefinitionKey(task.getTaskDefinitionKey());
			
			taskBO.setParentTaskId(task.getParentTaskId());
			taskBO.setStartTime(task.getCreateTime());
			taskBO.setTaskName(task.getName());
			taskBO.setProcessDefinitionId(task.getProcessDefinitionId());
			taskBO.setProcessInstanceId(task.getProcessInstanceId());
			
		}
		return taskDtos;
	}
	
}
